#include "ScriptIntelliSense.h"
#include "EditorScriptFile.h"
#include "EditorScriptIdentifiers.h"
#include "Document/TextDocument.h"
#include "Document/PieceTable.h"
#include <stack>
#include "Reflection/Function.h"


static RScriptIntelliSenseSubsystem* IntelliSense = nullptr;

RScriptIntelliSenseSubsystem::RScriptIntelliSenseSubsystem()
{
    IntelliSense=this;
}

void RScriptIntelliSenseSubsystem::PostLoadFromAsset()
{
    Initialize();
}

void RScriptIntelliSenseSubsystem::Initialize()
{
    GlobalNamespace=std::make_shared<CEditorScriptNamespace>();
}

void RScriptIntelliSenseSubsystem::Deinitialize()
{
    GlobalNamespace=nullptr;

    IntelliSense = nullptr;
}

RScriptIntelliSenseSubsystem *RScriptIntelliSenseSubsystem::Get()
{
    return IntelliSense;
}

TSharedObjectPtr<REditorScriptFile> RScriptIntelliSenseSubsystem::FindOrCreateScriptFile(TSharedObjectPtr<RTextDocument> InDocument)
{
    TSharedObjectPtr<REditorScriptFile> *ScriptFile = AllScriptFiles.Find(InDocument);
    if(ScriptFile)
    {
        return *ScriptFile;
    }
    else
    {
        TSharedObjectPtr<REditorScriptFile> NewScriptFile = NewObject<REditorScriptFile>(this);
        NewScriptFile->BindDocument(InDocument);
        AllScriptFiles.Add(InDocument, NewScriptFile);
        return NewScriptFile;
    }
}

void RScriptIntelliSenseSubsystem::Tick(float DeltaTime)
{
    auto Files=AllScriptFiles.Values();

    TVector<TSharedObjectPtr<REditorScriptFile>> FilesToUpdate;
    for(auto& File : Files)
    {
        if(File->bIsDirty)
        {
            FilesToUpdate.Add(File);
            File->bIsDirty=false;
        }
    }

    //1.clear 
    for(auto& File : FilesToUpdate)
    {
        File->Identifiers.Clear();
    }

    //1. update the syntax tokens
    for(auto& File : FilesToUpdate)
    {
        File->UpdateSyntaxTokens();
    }

    //2. guess the identifier type 
    for(auto& File : FilesToUpdate)
    {
        std::stack<std::shared_ptr<CEditorScriptNamespace>> NamespaceStack;
        NamespaceStack.push(GlobalNamespace);
        std::stack<std::shared_ptr<CEditorScriptClass>> ClassStack;
        std::shared_ptr<CEditorScriptFunction> CurrentFunction=nullptr;

        //the identifiers that just declared
        std::shared_ptr<CEditorScriptNamespace> JustDeclaredNamespace;
        std::shared_ptr<CEditorScriptClass> JustDeclaredClass;
        std::shared_ptr<CEditorScriptFunction> JustDeclaredFunction;

        std::stack<int64_t> NamespaceBraceLevels;
        std::stack<int64_t> ClassBraceLevels;
        int64_t FunctionBodyBraceLevel=-1; //brace level of function content

        int64_t BraceLevel=0; //how many braces are opened
        int64_t ParenthesesLevel=0;
        int64_t BracketLevel=0;  

        //any {} [] () will cause TotalBracketLevel cahnge
        int64_t TotalBracketLevel=0;

        CString Text=File->TextDocument->GetPieceTable().GetText();

        auto GetTokenContent=[&](int64_t TokenIndex)->CString
        {
            return Text.SubString(File->Tokens[TokenIndex].Range.StartCharIndex, File->Tokens[TokenIndex].Range.EndCharIndex-File->Tokens[TokenIndex].Range.StartCharIndex+1);
        };



        for(int64_t TokenIndex=0 ;TokenIndex< File->Tokens.Num();  )
        {
            auto& Token = File->Tokens[TokenIndex];
            if(Token.TokenType == EEditorScriptTokenType::Using)
            {
                Token.Color=NormalKeywordColor;
                TokenIndex++; //skip using

                auto ParseUsing=[&]()
                {
                
                    if(TokenIndex>=File->Tokens.Num())
                    {
                        return;
                    }

                    auto JustDeclaredNamespace=NamespaceStack.top();
                    while(true)
                    {
                        auto& CurrentToken = File->Tokens[TokenIndex];
                        if(CurrentToken.TokenType == EEditorScriptTokenType::Identifier)
                        {
                            CurrentToken.Color=NamespaceColor;

                            CString NamespaceName= GetTokenContent(TokenIndex);
                            auto Namespace= JustDeclaredNamespace->FindNamespace(NamespaceName);
                            if(Namespace.expired())
                            {
                                auto SharedNamespace=std::make_shared<CEditorScriptNamespace>();
                                SharedNamespace->Name=NamespaceName;
                                Namespace=SharedNamespace;
                                JustDeclaredNamespace->InnerNamespaces.Add(Namespace);
                                File->Identifiers.Add(SharedNamespace);
                            } 
                            else
                            {
                                File->Identifiers.Add(Namespace.lock());
                            }
                            
                        }
                        else
                        {
                            break;
                        }

                        TokenIndex++;

                        if(TokenIndex<File->Tokens.Num() &&
                            File->Tokens[TokenIndex].TokenType == EEditorScriptTokenType::Dot)
                        {
                            TokenIndex++;
                        }
                        else
                        {
                            break;
                        }
                    }
                };

                ParseUsing();
            
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::Namespace)
            {
                Token.Color=NormalKeywordColor;
                TokenIndex++; //skip namespace

                auto ParseNamespace=[&]()
                {
                    if(TokenIndex>=File->Tokens.Num())
                    {
                        return;
                    }

                    auto& CurrentToken = File->Tokens[TokenIndex];
                    if(CurrentToken.TokenType == EEditorScriptTokenType::Identifier)
                    {
                        CurrentToken.Color=NamespaceColor;

                        CString NamespaceName= GetTokenContent(TokenIndex);
                        auto Namespace= NamespaceStack.top()->FindNamespace(NamespaceName);
                        
                        if(Namespace.expired())
                        {
                            JustDeclaredNamespace=std::make_shared<CEditorScriptNamespace>();
                            JustDeclaredNamespace->Name=NamespaceName;
                            Namespace=JustDeclaredNamespace;
                            NamespaceStack.top()->InnerNamespaces.Add(Namespace);
                        }
                        else
                        {
                            JustDeclaredNamespace=Namespace.lock();
                        }
                        File->Identifiers.Add(JustDeclaredNamespace);

                        TokenIndex++;
                    }       
                };
            
                ParseNamespace();

                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::Class)
            {
                Token.Color=NormalKeywordColor;
                TokenIndex++; //skip class

                auto ParseClass=[&]()
                {
                    if(TokenIndex>=File->Tokens.Num())
                    {
                        return;
                    }

                    auto& CurrentToken = File->Tokens[TokenIndex];
                    if(CurrentToken.TokenType == EEditorScriptTokenType::Identifier)
                    {
                        CurrentToken.Color=ClassColor;

                        CString ClassName= GetTokenContent(TokenIndex);
                       
                        if(!ClassStack.empty())
                        {   
                            auto ParentClass= ClassStack.top();
                            auto FoundClass=ParentClass->FindClass(ClassName);
                            if(FoundClass.expired())
                            {
                                auto Class= std::make_shared<CEditorScriptClass>();
                                Class->Name=ClassName;
                                ParentClass->InnerClasses.Add(Class);
                                JustDeclaredClass=Class;
                            }
                            else
                            {
                                JustDeclaredClass=FoundClass.lock();
                            }
                            
                        }
                        else //if(JustDeclaredNamespace)
                        {
                            auto& ParentNamespace= NamespaceStack.top();
                            auto FoundClass=ParentNamespace->FindClass(ClassName);
                            if(FoundClass.expired())
                            {
                                auto Class= std::make_shared<CEditorScriptClass>();
                                Class->Name=ClassName;
                                ParentNamespace->Classes.Add(Class);
                                JustDeclaredClass=Class;
                            }
                            else
                            {
                                JustDeclaredClass=FoundClass.lock();
                            }
                        }
                        File->Identifiers.Add(JustDeclaredClass);
                        TokenIndex++;
                    }       
                };

                ParseClass();


                continue;
            }

            if(Token.TokenType==EEditorScriptTokenType::LeftBrace)
            {
                Token.Color=BracketColors[TotalBracketLevel%3];

                TokenIndex++;

                if(JustDeclaredFunction)
                {
                    CurrentFunction=JustDeclaredFunction;
                    JustDeclaredFunction=nullptr;
                    FunctionBodyBraceLevel=BraceLevel;
                }
                else if(JustDeclaredClass)
                {
                    //is parsing content of class
                    ClassStack.push(JustDeclaredClass);
                    ClassBraceLevels.push(BraceLevel);
                    JustDeclaredClass=nullptr;
                    
                }
                else if(JustDeclaredNamespace)
                {
                    //is parsing content of namespace
                    NamespaceStack.push(JustDeclaredNamespace);
                    NamespaceBraceLevels.push(BraceLevel);
                    JustDeclaredNamespace=nullptr;
                }
                
                BraceLevel++;
                TotalBracketLevel++;
                continue;
            }

            if(Token.TokenType==EEditorScriptTokenType::RightBrace)
            {
                BraceLevel--;
                TotalBracketLevel--;
                
                Token.Color=BracketColors[TotalBracketLevel%3];
                TokenIndex++;

                if(FunctionBodyBraceLevel==BraceLevel)
                {
                    //leave function body
                    CurrentFunction=nullptr;
                    FunctionBodyBraceLevel=-1;
                }
                else if(!ClassBraceLevels.empty() && ClassBraceLevels.top()==BraceLevel)
                {
                    //leave the class body
                    ClassStack.pop();
                    ClassBraceLevels.pop();
                }
                else if(!NamespaceBraceLevels.empty() && NamespaceBraceLevels.top()==BraceLevel)
                {
                    //leave the namespace body
                    NamespaceStack.pop();
                    NamespaceBraceLevels.pop();
                }
                
                continue;                

            }

            if(Token.TokenType==EEditorScriptTokenType::LeftParentheses)
            {
                Token.Color=BracketColors[TotalBracketLevel%3];
                TokenIndex++;

                TotalBracketLevel++;
                ParenthesesLevel++;

                continue;
            }

            if(Token.TokenType==EEditorScriptTokenType::RightParentheses)
            {
                TotalBracketLevel--;
                ParenthesesLevel--;

                Token.Color=BracketColors[TotalBracketLevel%3];
                TokenIndex++;
            
                continue;
            }

            if(Token.TokenType==EEditorScriptTokenType::LeftBracket)
            {
                Token.Color=BracketColors[TotalBracketLevel%3];
                TokenIndex++;

                TotalBracketLevel++;
                BracketLevel++;

                continue;
            }

            if(Token.TokenType==EEditorScriptTokenType::RightBracket)
            {
                TotalBracketLevel--;
                BracketLevel--;

                Token.Color=BracketColors[TotalBracketLevel%3];
                TokenIndex++;

                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::Comment)
            {
                Token.Color=CommentColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::LiteralString)
            {
                Token.Color=LiteralStringColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::LiteralNumber)
            {
                Token.Color=LiteralNumberColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::LiteralBool)
            {
                Token.Color=LiteralBooleanColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::NormalKeyword)
            {
                Token.Color=NormalKeywordColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::Identifier)
            {
                CString IdentifierName= GetTokenContent(TokenIndex);

                //check if it is a variable of current function
                if(CurrentFunction)
                {
                    auto FoundIdentifier=CurrentFunction->FindVariable(IdentifierName);
                    if(!FoundIdentifier.expired())
                    {
                        Token.Color=VariableColor;
                        ++TokenIndex;

                        auto & NextToken=File->Tokens[TokenIndex];
                        if(NextToken.TokenType == EEditorScriptTokenType::Dot)
                        { 
                            //parse the identifier chain
                            auto ParseNamedChain=[&]()
                            {
                                CType* CurrentType=FoundIdentifier.lock()->NativeType;
                                if(!CurrentType)
                                {
                                    return;
                                }

                                while(true)
                                {
                                    if(TokenIndex>=File->Tokens.Num())
                                    {
                                        break;
                                    }

                                    auto DotToken=File->Tokens[TokenIndex]; 
                                    if(DotToken.TokenType == EEditorScriptTokenType::Dot)
                                    {
                                        DotToken.Color=DotColor;
                                        TokenIndex++;
                                    }
                                    else
                                    {
                                        break;
                                    }

                                    if(TokenIndex>=File->Tokens.Num())
                                    {
                                        break;
                                    }

                                    if(File->Tokens[TokenIndex].TokenType == EEditorScriptTokenType::Identifier)
                                    {
                                        //this is a member of the class
                                        CString MemberName= GetTokenContent(TokenIndex);
                                        if(auto Function=CurrentType->FindFunction(MemberName))
                                        {
                                            File->Tokens[TokenIndex].Color=FunctionColor;
                                            TokenIndex++;

                                            //TODO because the function always followed by (),
                                            // so we need a good way to skip the ()
                                            break;
                                        }
                                        else if(auto Property=CurrentType->FindProperty(MemberName))
                                        {
                                            File->Tokens[TokenIndex].Color=FieldColor;
                                            TokenIndex++;

                                            CurrentType=Property->GetType().BasicType;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                    else
                                    {
                                        break;
                                    }

                                }
                            };
                        
                            ParseNamedChain();
                        }

                        continue;
                    }
                }

                //check if it is a member of current class
                if(!ClassStack.empty())
                {
                    auto& CurrentClass= ClassStack.top();
                    auto FoundIdentifier=CurrentClass->FindFunction(IdentifierName);
                    if(!FoundIdentifier.expired())
                    {
                        Token.Color=FunctionColor;
                        ++TokenIndex;
                        continue;
                    }
                }

                //check if it is a field of current class
                if(!ClassStack.empty())
                {
                    auto& CurrentClass= ClassStack.top();
                    auto FoundIdentifier=CurrentClass->FindField(IdentifierName);
                    if(!FoundIdentifier.expired())
                    {
                        Token.Color=NormalKeywordColor;
                        ++TokenIndex;
                        continue;
                    }
                }

                //check if it is a class name
                auto Type=CType::FindType(IdentifierName);
                if(Type)
                {
                    Token.Color=ClassColor;
                    ++TokenIndex;

                    if(TokenIndex>=File->Tokens.Num())
                    {
                        continue;
                    }

                    auto NextToken=File->Tokens[TokenIndex];
                    if(NextToken.TokenType == EEditorScriptTokenType::Dot)
                    { 
                        //parse the identifier chain
                        auto ParseNamedChain=[&]()
                        {
                            CType* CurrentType=Type;
                            while(true)
                            {
                                if(TokenIndex>=File->Tokens.Num())
                                {
                                    break;
                                }

                                auto DotToken=File->Tokens[TokenIndex];
                                if(DotToken.TokenType == EEditorScriptTokenType::Dot)
                                {
                                    DotToken.Color=DotColor;
                                    TokenIndex++;
                                }
                                else
                                {
                                    break;
                                }

                                if(TokenIndex>=File->Tokens.Num())
                                {
                                    break;
                                }

                                if(File->Tokens[TokenIndex].TokenType == EEditorScriptTokenType::Identifier)
                                {
                                    //this is a member of the class
                                    CString MemberName= GetTokenContent(TokenIndex);
                                    if(auto Function=CurrentType->FindFunction(MemberName))
                                    {
                                        File->Tokens[TokenIndex].Color=FunctionColor;
                                        TokenIndex++;

                                        //TODO because the function always followed by (),
                                        // so we need a good way to skip the ()
                                        break;
                                    }
                                    else if(auto Property=CurrentType->FindProperty(MemberName))
                                    {
                                        File->Tokens[TokenIndex].Color=FieldColor;
                                        TokenIndex++;

                                        CurrentType=Property->GetType().BasicType;
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        };

                        ParseNamedChain();
                    }
                    else if(NextToken.TokenType == EEditorScriptTokenType::Identifier)
                    {
                        if(CurrentFunction)
                        {
                            //this is a variable
                            NextToken.Color=VariableColor;
                            auto Variable=std::make_shared<CEditorScriptVariable>();
                            Variable->Name= GetTokenContent(TokenIndex);
                            Variable->NativeType=Type;
                            CurrentFunction->Variables.Add(Variable);

                            File->Identifiers.Add(Variable);
                            File->Tokens[TokenIndex].Color=VariableColor;

                            ++TokenIndex;
                            continue;
                        }


                    }

                    continue;
                }

                auto Class=GlobalNamespace->FindClassRecursively(IdentifierName);
                if(!Class.expired())
                {
                    Token.Color=ClassColor;
                    ++TokenIndex;
                    continue;
                }

                //donot know what it is
                //if followed a ( assume it is a function color

                if(File->Tokens.Num()>(TokenIndex+1)
                && File->Tokens[TokenIndex+1].TokenType == EEditorScriptTokenType::LeftParentheses)
                {
                    Token.Color=FunctionColor;
                    ++TokenIndex;
                    continue;
                }

                //assume it is a variable
                Token.Color=VariableColor;
                ++TokenIndex;
                continue;
            }

            if(Token.TokenType == EEditorScriptTokenType::InnerType)
            {
                Token.Color=NormalKeywordColor;
                ++TokenIndex;

                //next token might be a field / function/ variable
                if(TokenIndex>=File->Tokens.Num())
                {
                    continue;
                }

                auto& NextToken=File->Tokens[TokenIndex];
                if(NextToken.TokenType == EEditorScriptTokenType::Identifier)
                {
                    CString IdentifierName= GetTokenContent(TokenIndex);

                    if(CurrentFunction)
                    {
                        //this is a variable
                        NextToken.Color=VariableColor;
                        auto Variable=std::make_shared<CEditorScriptVariable>();
                        Variable->Name=IdentifierName;
                        CurrentFunction->Variables.Add(Variable);

                        File->Identifiers.Add(Variable);
                        File->Tokens[TokenIndex].Color=VariableColor;

                        ++TokenIndex;
                        continue;
                    }

                    if(ClassStack.empty())
                    {
                        continue;
                    }

                    auto& CurrentClass= ClassStack.top();
                    if(File->Tokens.Num()>(TokenIndex+1) && File->Tokens[TokenIndex+1].TokenType == EEditorScriptTokenType::LeftParentheses)
                    {
                        //this is a function
                        NextToken.Color=FunctionColor;

                        auto Function=std::make_shared<CEditorScriptFunction>();
                        Function->Name=IdentifierName;
                        CurrentClass->Functions.Add(Function);

                        File->Identifiers.Add(Function);
                        File->Tokens[TokenIndex].Color=FunctionColor;

                        JustDeclaredFunction=Function;

                        TokenIndex++;
                        continue;
                    }
                    else
                    {
                        //this is a field
                        NextToken.Color=FieldColor;

                        auto Field=std::make_shared<CEditorScriptField>();
                        Field->Name=IdentifierName;
                        CurrentClass->Fields.Add(Field);

                        File->Identifiers.Add(Field);
                        File->Tokens[TokenIndex].Color=FieldColor;

                        ++TokenIndex;
                        continue;
                    }
                    

                }
                continue;

            }


            Token.Color=UnknowColor;
            ++TokenIndex;

        }

    }



    //3. update the colors
    for(auto& File : FilesToUpdate)
    {
        File->UpdateColors();
    }
}
